/*
 * Copyright 2011 PA Consulting Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.prodeagle.java;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import com.google.appengine.api.memcache.AsyncMemcacheService;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;

/**
 * Helper methods for storing values into MemCache - implements some
 * of the methods that the Python API has, but the Java API does not
 * @author Edward Hartwell Goose
 *
 */
public class MemCacheManager implements ProdEagleConstants {

	private static final Logger _logger = Logger.getLogger(MemCacheManager.class.getName());
	private static final Future<?> _futureEmptyMap = ImmediateFuture.of(Collections.emptyMap());
	private static final Future<?> _futureEmptySet = ImmediateFuture.of(Collections.emptySet());

	/**
	 * Equivalent of pythons offset_multi function. Stores multiple counters and their values.
	 *
	 * @param allCounters - the map of counters and their values to store
	 * @param namespace - the memcache namespace
	 * @param slotPrefix - the slot these counters are for
	 * @param initalValue - the inital value if they have none
	 * @return - the values stored in memcache
	 */
	public static Future<Map<String, Long>> storeMultipleCounters(Map<String, Long> allCounters, String namespace, String slotPrefix, long initalValue) {
		if (null == allCounters || allCounters.isEmpty()) {
			return futureEmptyMap();
		}

		Map<String, Long> prefixedCounters = new HashMap<String, Long>();

		for (String counterName : allCounters.keySet()) {
			prefixedCounters.put(slotPrefix + "_" + counterName, allCounters.get(counterName));
		}

		return storeMultipleCounters(prefixedCounters, namespace, initalValue);
	}

	/**
	 * Equivalent of pythons offset_multi function. Stores multiple counters and their values, with no prefix on the counter names
	 *
	 * @param allCounters - the map of counters and their values
	 * @param namespace - the memcache namespace to store the counters in
	 * @param initalValue - the inital value to store
	 * @return - the values stored in memcache
	 */
	public static Future<Map<String, Long>> storeMultipleCounters(Map<String, Long> allCounters, String namespace, long initalValue) {
		if (null == allCounters || allCounters.isEmpty()) {
			return futureEmptyMap();
		}

		AsyncMemcacheService memcacheService = MemcacheServiceFactory.getAsyncMemcacheService(namespace);

		return memcacheService.incrementAll(allCounters, initalValue);
	}

	/**
	 * Get a set of counters from memcache
	 *
	 * @param allCounterNames - all the counter names we want the values for
	 * @param namespace - the memcache namespace to use
	 * @return - the memcache values currently stored
	 */
	public static Future<Map<String, Object>> getMultipleCounters(Collection<String> allCounterNames, String namespace) {
		if (null == allCounterNames || allCounterNames.isEmpty()) {
			return futureEmptyMap();
		}

		AsyncMemcacheService memcacheService = MemcacheServiceFactory.getAsyncMemcacheService(namespace);

		return memcacheService.getAll(allCounterNames);
	}

	/**
	 * Get a set of counters from memcache
	 * @param counterNames - all the counter names we currently know about
	 * @param slot - the slot to prefix the keys for
	 * @param namespace - the memcache namespace to use
	 * @return - the memcache values currently stored
	 */
	public static Map<String, Object> getMultipleCounters(Collection<String> counterNames, String slotPrefix, String namespace) {
		if (null == counterNames || counterNames.isEmpty()) {
			return Collections.emptyMap();
		}

		Set<String> prefixedCounters = new HashSet<String>();
		for (String name : counterNames) {
			prefixedCounters.add(slotPrefix + "_" + name); //change each counters name to the slotPrefix_countername
		}

		MemcacheService memcacheService = MemcacheServiceFactory.getMemcacheService(namespace);

		return memcacheService.getAll(prefixedCounters);
	}
	
	/**
	 * Delete multiple counters from memcache in the given namespace
	 * @param counterNames - the names of all the counters to delete
	 * @param namespace
	 */
	public static Future<Set<String>> deleteMulti(Collection<String> counterNames, String namespace) {
		if (null == counterNames || counterNames.isEmpty()) {
			return futureEmptySet();
		}

		_logger.info("Deleting counter keys (count: " + counterNames.size() + ")");
		AsyncMemcacheService memcacheService = MemcacheServiceFactory.getAsyncMemcacheService(namespace);

		return memcacheService.deleteAll(counterNames);
	}

	public static void clearMemCache() {
		MemcacheService memcacheService = MemcacheServiceFactory.getMemcacheService(NAMESPACE);
		memcacheService.clearAll();
	}

	@SuppressWarnings("unchecked")
	private static <T> Future<Set<T>> futureEmptySet() {
		return (Future<Set<T>>) _futureEmptySet;
	}
	
	@SuppressWarnings("unchecked")
	private static <K, V> Future<Map<K, V>> futureEmptyMap() {
		return (Future<Map<K, V>>) _futureEmptyMap;
	}

	private static final class ImmediateFuture<V> implements Future<V> {
		private final V value;
	
		ImmediateFuture(V value) {
			this.value = value;
		}
		
		static <V> ImmediateFuture<V> of(V value) {
			return new ImmediateFuture<V>(value);
		}
		
		@Override public boolean cancel(boolean arg0) {
			return false;
		}
		
		@Override public V get() {
			return value;
		}
		
		@Override public V get(long duraton, TimeUnit unit) {
			return get();
		}
		
		@Override public boolean isCancelled() {
			return false;
		}
		
		@Override public boolean isDone() {
			return true;
		}
	}
}
